/*
 * Copyright (c) 2010, Apropos Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name Apropos nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package org.apropos.model;

import com.rallydev.webservice.v1_21.domain.Project;
import com.rallydev.webservice.v1_21.domain.User;
import com.rallydev.webservice.v1_21.service.RallyService;
import com.rallydev.webservice.v1_21.service.RallyServiceServiceLocator;
import javafx.scene.image.Image;
import javafx.stage.Alert;
import javafx.util.Math;
import org.apache.axis.client.Stub;
import org.apache.axis.AxisFault;
import org.jfxtras.lang.XObject;
import org.jfxtras.util.SequenceUtil;
import javafx.util.Sequences;

/**
 * @author Stephen Chin
 * @author Keith Combs
 */
public def APROPOS_VERSION = "0.8.14";

public var readOnly:Boolean;

def community:Boolean = false;
def show:Boolean = false;
public def server = bind if (community) "https://community.rallydev.com/" else if (show) "https://show.rallydev.com/" else "https://rally1.rallydev.com/";

def GUEST_USER = if (community) "apropos@jfxtras.org" else if (show) "peggy@acme.com" else "build@inovis.com";
def GUEST_PASSWORD = if (community) "AproposFX" else if (show) "4apropos" else "StephenIsCool";

public def instance = RallyModel {}

public class RallyModel extends XObject {
    var warned = false;
    public var login = Login {userName: GUEST_USER, password: GUEST_PASSWORD};
    public var loggedIn = false;
    public var processingLogin:Boolean = false;
    public var showInDollars = false;
    public var showActual = false;
    public-init var releasePlanNames = ["2012 Q2", "2012 Q3", "2012 Q4"];
    public-init var currentRelease:Release;
    public-init var iterations = if (show) ["Iteration 5 (R2)", "Iteration 6 (R2)", "Iteration 7 (R2)", "Iteration 8 (R3)", "Iteration 9 (R3)", "Iteration 10 (R3)"]
    else ["Sprint 2012-05-15", "Sprint 2012-05-29", "Sprint 2012-06-12", "Sprint 2012-06-26", "Sprint 2012-07-10", "Sprint 2012-07-24" ];
    public-init var ownerNames:String[] =
      if (community) ["vaan@jfxtras.org", "ashe@jfxtras.org", "basch@jfxtras.org", "penelo@jfxtras.org", "balthier@jfxtras.org", "fran@jfxtras.org"]
      else if (show) ["dave@acme.com", "srampson@rallydev.com", "peggy@acme.com", "sara@acme.com", "tara@acme.com", "tom@acme.com"]
      else [
      "dayananda.bs@gxs.com",
      "benjamin.bunting@gxs.com",
      "tracey.davies@gxs.com",
      "tom.aydelotte@inovis.com",
      "gary.kostalnick@inovis.com",
      "gail.jennings@gxs.com",
      "srinivasan.sairam@gxs.com",
      "shiv.patil@gxs.com",
      "scott.bourdeau@gxs.com",
      "scott.byrns@gxs.com",
      "thomas.peterson@gxs.com",
      "scott.mcwilliams@gxs.com",
      "kirk.farrar@gxs.com",
      "deborah.berryman@gxs.com",
      "swapnil.gupta@gxs.com",
      "manisha.m@gxs.com",
      "ricardo.ceppi@gxs.com",
      "sean.ogorman@gxs.com",
      "fernandez.a@gxs.com",
      "kris.loia@inovis.com",
      "edward.brailovsky@gxs.com",
      "gopalakrishna.s@gxs.com",
      "naveen.kumar@gxs.com",
      "shankar.shanmugam@gxs.com",
      "geny.george@gxs.com",
      "sivalinga.pandian@gxs.com",
      "marimuthu.p@gxs.com",
      "pradeep.kochale@gxs.com",
      "narasimha.bhat@gxs.com",
      "pradeep.kr@gxs.com",
      "srilatha.s@gxs.com",
      "tim.geldart@inovis.com",
      "greg.conant@inovis.com",
      "mehdi.rakhshani@gxs.com",
      "patrick.moynihan2@inovis.com",
      "leandro.moreira@gxs.com",
      "john.clay@gxs.com"];
    public-init var owners:User[];
    public-read var myUser:User;
    public-read var myImage:Image;
    public-read var ownerImages:Image[];
    public-init var initialTargets = [
    "398",// ace itc
    "612",// ace ms
    "120",// alpha
    "240",// armadillos
    "300",// biz comm
    "350",// blue crabs
    "210",// callisto
    "110",// checkered flag
    "309",// Rollstream
    "156",// code titans
    "210",// commPassion
    "41",// Conf Prod
    "129",// Cosmic Debris
    "64",// Diamond Backs
    "470",// ebuddahs & team horizon
    "280",// elite
    "189",// frying pan
    "366",// Gateway1,2,3
    "220",// kaizen
    "189",// Lone star
    "170",// MS-ADV-ENG
    "166",// Nimble wits
    "230",//Oceans 10
    "625",// pathfinders & quicksilver
    "300",//phoenix
    "144",//rally express
    "210",//royal challengers
    "70",//super kings
    "300",// hermes
    "262",// the himalayan
    "210",// the transporters
    "189",// TKOs
    "225",// TLI
    "425",// Tsunami Crush
    "180",// Turbo Squadron
    "153",//world cup
    "845"// john clay,3 teams
    ];
    public-init var estimateToActual = [
      1.669767442,// ACE ITC
      2,// ACE MS
      0.683392149,// alpha
      0.730184766,// armadillos
      0.886177121,// biz comms
      1.26984127,// blue crabs
      0.144444444,// callisto
      1.809210526,// checkered flag
      1.924634921,// code titans
      0.917676768,// commPassion
      0.666666667,// conf prod
      1.172172619,// cosmic debris/inovisworks team
      2, // Diamond backs
      0.926,// e-buddahs-horizon avg
      0.995377319,// elite
      0.765173611,// frying pan
      2,// gateway
      1.172172619,// kaizen
      1.773159722,// Lone star
      2,// MS-ADV-ENG
      1.338573464,// nimble wits
      0.752580229, //Oceans 10
      0.6686,// pathfinders/quicksilver avg
      1.202826797,// phoenix
      0.891746032,// rally express
      1.39457672,// royal challengers
      0.857638889,// super kings
      2.333333333,// hermes
      1.25455813,// the himalayans
      1.764957265,// the transporters
      1.433763228,// tkos
      1.526163073,// tli
      2.102460183,// tsunami crush
      1.171042716,// turbo squadron
      2.498830409,// world cup
      2//john clay
    ];
    public-init var stageNames = ["Propose", "Analyze", "Schedule", "Develop", "Deploy", "Enable", "Adopt", "Validate"];
    public-init var themeRatios = [.05, .38, .27, .16, .03, .08, .01];
    public-init var wipLimits = [0.0, 9600.0, 2400.0, 1000.0, 0, 12, 12, 0];
    public-init var wipLimitByCount = [false, false, false, false, false, true, true, false];
    public-init var estimateToActualRatio = 4.53;
    public-init var actualToCostRatio = 66.5;
    public-read var rallyService:RallyService;
    public var backlog:Backlog;
    public-init var releaseNames = ["Internal Release 2012Q1", "Internal Release 2012Q2", "Internal Release 2012Q3"];
    public var releases:Release[];
    public var stages:Stage[];

    public var epicNames:String[];

    public var allocationNames:String[];
    public var selectedAllocationIndex:Integer;
    public var selectedAllocation:String = bind if (selectedAllocationIndex == 0) null else {
        allocationNames[selectedAllocationIndex - 1];
    }
    public var productNames:String[];

    //TODO: Remove the concept of packages, in favor of Portfolio Allocations
    public var packageNames:String[];
    public var selectedPackageIndex:Integer;
    public var selectedPackage:String = bind if (selectedPackageIndex == 0) null else {
        packageNames[selectedPackageIndex - 1];
    }

    public var selectedOwnerIndex:Integer;
    public var selectedOwner:String = bind if (selectedOwnerIndex == 0) null else if (selectedOwnerIndex == 1) "no owner" else {
        owners[selectedOwnerIndex - 2].getDisplayName();
    }
    public var inScopeIndex:Integer;
    public var inScope:String = bind if (inScopeIndex == 0) "YN" else if (inScopeIndex == 1) "Y" else "N";
    public var mainProjectName = if (community) "" else if (show) "Online Store" else "Business Community Management";
    public-read var mainProject:Project;
    public var waiting = 0;

    bound function filtersOn() {
        return selectedAllocation != null or selectedOwner != null;
    }

    bound function selected(s:Story) {
        (selectedAllocation == null or s.portfolioAllocation == selectedAllocation) and
        (selectedOwner == null or s.ownerDisplayName == selectedOwner) and
        inScope.contains(s.inScopeDisplay)
    }

    public bound function filter(stories:Story[]) {
        if (not filtersOn()) stories else stories[s|selected(s)];
    }

    public function doLogin():Void {
        try {
            processingLogin = true;
            readOnly = login.userName == GUEST_USER and not show;

            createService();
            loadReleases();
            loadOwners();

            loggedIn = true;
        } catch (e:AxisFault) {
            processingLogin = false;
            Alert.inform("Login Failed", "Login failed to Rally.  Please double check your username and password.");
        } finally {
          processingLogin = false;
        }
    }

    function loadReleases() {
// todo - release fix
//        def rallyReleases = rallyService.query(null, mainProject, false, false, "Release", null, null, true, 0, 100).getResults();
//        def now = Calendar.getInstance();
        backlog = Backlog {model: this}
//        releases = for (r in rallyReleases where (r as com.rallydev.webservice.v1_21.domain.Release).getReleaseDate().<<after>>(now)) Release {release: r as com.rallydev.webservice.v1_21.domain.Release, model: this}
        releases = for (rpn in releasePlanNames) Release {model: this, name: rpn}
        for (r in releases) {
//            if (r.release.getReleaseStartDate().<<before>>(now) and r.release.getReleaseDate().<<after>>(now)) currentRelease = r;
            r.containerBefore = releases[indexof r - 1];
            r.containerAfter = releases[indexof r + 1];
        }
        currentRelease = releases[0];
        backlog.containerAfter = releases[0];
        releases[0].containerBefore = backlog;
        stages = for (stageName in stageNames) Stage {name: stageName}
        for (s in stages) {
            s.containerBefore = stages[indexof s - 1];
            s.containerAfter = stages[indexof s + 1];
        }
    }

    function loadOwners() {
        def myResult = rallyService.query(null, mainProject, false, false, "User", "(UserName = \"{login.userName}\")", null, true, 0, 100).getResults();
        if (sizeof myResult > 0) {
            myUser = myResult[0] as User;
        } else {
            println("Unable to get credentials for users -- please update account priveleges");
        }
        owners = for (ownerName in ownerNames) {
            def results = rallyService.query(null, mainProject, false, false, "User", "(UserName = \"{ownerName}\")", null, true, 0, 100).getResults();
            if (sizeof results == 0) null else results[0] as User;
        }
        // hack to login for image retrieval
        Image {url: "{server}slm/j_spring_security_check?j_username={login.userName}&j_password={login.password}"}
        Image {url: "{server}slm/j_spring_security_check?j_username={login.userName}&j_password={login.password}"}
        myImage = Image {
            width: 70
            height: 70
            preserveRatio: true
            url: "{server}slm/profile/viewThumbnailImage.sp?tSize=200&uid={myUser.getObjectID()}"
        }
        ownerImages = for (owner in owners) {
            Image {
                width: 70
                height: 70
                preserveRatio: true
                url: "{server}slm/profile/viewThumbnailImage.sp?tSize=200&uid={owner.getObjectID()}"
            }
        }
    }

    public function getRelease(releasePlanName:String) {
        return releases[r|r.name == releasePlanName][0];
    }

    public bound function limitByCount(stageIndex:Integer):Boolean {
        return wipLimitByCount[stageIndex];
    }

    public bound function getWipLimit(stageIndex:Integer):Double {
        def multiplier = SequenceUtil.product(for (p in packageNames) {
            if (p == selectedPackage) {
                themeRatios[indexof p];
            } else {
                1
            }
        });
        def rawLimit = wipLimits[stageIndex] * multiplier;
        return if (wipLimitByCount[stageIndex]) Math.ceil(rawLimit) else rawLimit;
    }

    public bound function overSubLimit(stageIndex:Integer):Boolean {
        return false;
//        def packagesOver = for (p in packageNames where overThemeLimit(stageIndex, indexof p, p)) p;
//        return sizeof packagesOver > 0;
    }

// causes a big performance issues - need to investigate further...
//    bound function overThemeLimit(stageIndex:Integer, themeIndex:Integer, theme:String) {
//        def limitByCount = wipLimitByCount[stageIndex];
//        def stageLimit = wipLimits[stageIndex];
//        def stage = stages[stageIndex];
//        def themeLimit = stageLimit * themeRatios[themeIndex];
//        def filteredStories = stage.stories[s|s.inPackage == theme];
//        def filteredCount = sizeof filteredStories;
//        def filteredSize = SequenceUtil.sum(for (story in filteredStories) story.estimate);
//        def themeTotal = if (limitByCount) filteredCount else filteredSize;
//        themeTotal > themeLimit;
//    }

    public function requestAccess():Boolean {
        if (readOnly and not warned) {
            Alert.inform("View is read only.  Changes you make will not be saved!");
            warned = true;
        }
        return not readOnly;
    }

    public function refresh():Void {
        for (stage in stages) stage.stories = null;
        for (r in [backlog, releases]) r.refresh();
    }

    public function findStage(stageName:String):Stage {
        if (stageName == null) return null;
        for (stage in stages) {
            if (stage.name.equalsIgnoreCase(stageName)) {
                return stage;
            }
        }
        return null;
    }

    function createService():Void {
        rallyService = new RallyServiceServiceLocator().getRallyService();

        var stub = rallyService as Stub;
        stub.setUsername(login.userName);
        stub.setPassword(login.password);

        stub.setMaintainSession(true);
        // todo - need to create a project selector dialog
        def projects = rallyService.query(null, "Project", null, null, true, 0, 100).getResults();
        for (project in projects) {
            def p = project as Project;
            if (p.getName() == mainProjectName) {
                mainProject = p;
            }
        }
        if (mainProject == null) {
            mainProject = projects[0] as Project;
        }
    }

    public bound function getActual(estimate:Double, owner:User):Double {
        return estimate * estimateToActual[Sequences.indexOf(for (o in owners) o.getRef(), owner.getRef())];
    }

    public bound function convertEstimate(estimate:Double):String {
        return if (showInDollars) {
            def dollars = estimate * estimateToActualRatio * actualToCostRatio;
            if (dollars >= 10000000) {
                "${%.0f dollars/1000000}M";
            } else if (dollars >= 1000000) {
                "${%.1f dollars/1000000}M";
            } else if (dollars >= 10000) {
                "${%.0f dollars/1000}K";
            } else if (dollars >= 1000) {
                "${%.1f dollars/1000}K";
            } else {
                "${%.0f dollars}";
            }
        } else {
            "{ %.0f estimate}";
        }
    }

    public function getOwnerDisplayName(ownerRef:String):String {
        var displayName:String = "no owner";
        for (owner in owners) {
            if (owner.getRef() == ownerRef) {
                displayName = owner.getDisplayName();
                break;
            }
        }
        return displayName;
    }

}
